#!/usr/bin/python2.5
# Copyright 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Storage for configuration settings.  Settings can be global or specific
to a subdomain, and their values can be of any JSON-encodable type."""

from google.appengine.ext import db
import UserDict, model, random, simplejson
import logging
import datetime
import utils
from datetime import timedelta


class ConfigurationCache:
    """This class implements an in-memory cache used to store the config
    entries. Cache entries have a default of 600seconds lifetime. When 
    fetching a config entry, the cache is first searched. If the entry is 
    not available in cache it is retrieved from database, added to cache and
    returned. Cache is enabled by setting a config entry *:config_cache_enable.
    Config entries are stored with the key subdomain:entry_name in database.
    This cache uses subdomain name as the key and stores all configs for a
    subdomain in one cache element. The global configs have a subdomain '*'. """
    storage = {}
    expiry_time=600
    miss_count=0
    hit_count=0
    evict_count=0
    items_count=0
    max_items=0

    def flush(self):
        self.storage.clear()
        self.items_count=0
        
    def delete(self,key):
        """Deletes the entry with given key from config_cache."""
        if key in self.storage:
            self.storage.pop(key)
            self.items_count -= 1 

    def add(self, key, value, time_to_live_in_seconds):
        """Adds the key/value pair to cache and updates the expiry time.
           If key already exists, its value and expiry are updated."""
        expiry = utils.get_utcnow() + timedelta(seconds=time_to_live_in_seconds)
        self.storage[key] = (value, expiry)
        self.items_count += 1
        self.max_items += 1

    def read(self, key, default=None):
        """Gets the value corresponding to the key from cache. If cache entry
           has expired, it is deleted from the cache and None is returned."""
        value, expiry = self.storage.get(key, (None,0))
        if value is None :
            self.miss_count += 1
            return default
        
        now = utils.get_utcnow()
        if (expiry > now) :
            self.hit_count += 1
            return value
        else:
            # Stale cache entry. Evicting from cache
            self.delete(key)
            self.evict_count += 1
            self.miss_count += 1
            return default


    def stats(self):
        logging.info("Hit Count - %r" % self.hit_count)
        logging.info("Miss Count - %r" % self.miss_count)
        logging.info("Items Count - %r" % self.items_count)
        logging.info("Eviction Count - %r" % self.evict_count)
        logging.info("Max Items - %r" % self.max_items)
    
    def get_config(self, subdomain, name, default=None):
        """Looks for data in cache. If not present, retrieves from
           database, stores it in cache and returns the required value."""
        config_dict = self.read(subdomain, None)
        if config_dict is None:
            # Cache miss 
            entries = model.filter_by_prefix(ConfigEntry.all(), 
                                                    subdomain + ':')
            if entries is None:
                return default
            logging.debug("Adding Subdomain %r to config_cache" % subdomain)
            config_dict = dict([(e.key().name().split(':', 1)[1],
                         simplejson.loads(e.value)) for e in entries])  
            self.add(subdomain, config_dict, self.expiry_time)

        element = config_dict.get(name)

        if element == None:
            return default      
        return element
            
    def enable(self, value):
        """Enable/disable caching of config."""
        logging.info('Setting config_cache_enable to %s' % value)
        db.put(ConfigEntry(
              key_name="*:config_cache_enable", value=simplejson.dumps(bool(value))))
        self.delete('*')
                
    def is_enabled(self):
        return self.get_config('*','config_cache_enable', None)

cache = ConfigurationCache()    

class ConfigEntry(db.Model):
    """An application configuration setting, identified by its key_name."""
    value = db.TextProperty(default='')

def make_key(subdomain, key):
    return subdomain + ':' + key
    
def get(name, subdomain=None, default=None):
    """Gets a configuration setting from cache if it is enabled,
       otherwise from the database."""
    if subdomain is None:
        subdomain = '*'
    if cache.is_enabled():
        return cache.get_config(subdomain, name, default)
    else:
        config = ConfigEntry.get_by_key_name(make_key(subdomain,name))
        if config:
            return simplejson.loads(config.value)
        return default

def set(subdomain=None, **kwargs):
    """Sets configuration settings."""
    if subdomain is None:
        subdomain = '*'
    db.put(ConfigEntry(key_name=make_key(subdomain,name), 
           value=simplejson.dumps(value)) for name, value in kwargs.items())
    cache.delete(subdomain)
    
def get_for_subdomain(subdomain, name, default=None):
    """Gets a configuration setting for a particular subdomain.  Looks for a
    setting specific to the subdomain, then falls back to a global setting."""
    value = get(name, subdomain, default)
    if value != default:
        return value
    return get(name, '*', default)

def set_for_subdomain(subdomain, **kwargs):
    """Sets configuration settings for a particular subdomain.  When used
    with get_for_subdomain, has the effect of overriding global settings."""
    set(str(subdomain), **kwargs)

class Configuration(UserDict.DictMixin):
    def __init__(self, subdomain):
        self.subdomain = subdomain

    def __nonzero__(self):
        return True

    def __getattr__(self, name):
        return self[name]

    def __getitem__(self, name):
        """Gets a configuration setting for this subdomain.  Looks for a
        subdomain-specific setting, then falls back to a global setting."""
        return get_for_subdomain(self.subdomain, name)

    def keys(self):
        entries = model.filter_by_prefix(
            ConfigEntry.all(), self.subdomain + ':')
        return [entry.key().name().split(':', 1)[1] for entry in entries]

